在介紹原型之前,我們要先來複習物件的內容
當我們要定義一隻狗的時候,我們會利用物件的資料結構,來對這隻狗進行描述。
例如它的顏色、體型大小,以及可以吠叫的方法。
主要的原因就是因為 JS 基本上都是由物件的方法去構成資料。
而原型呢也是一樣的概念喔!
在前一篇文章,類別繼承中我們有提到,如果我們要定義一個像是這樣的內容,我們會使用 class 來定義它。
但是 JS 中都是使用物件來定義,所以我們在定義原型的時候也是使用物件的方式來定義。
如果狗要透過這個原型來建立實體的話,也是透過繼承的方式繼承了原型的屬性跟方法,因此這就是兩個物件的概念。
原型的狗就會有顏色、體型大小,以及可以吠叫的方法。而在實體的狗呢這些屬性就會有可以自定義的空間。
那麼吠叫的部分就可以繼承原本原型的方法。
實際上,當我們運行JS的時候,新增一個物件的實體的時候就會有屬於該實體的屬性
那麼原型的部分依樣也會有它自己的屬性,那這樣的結構上依然是屬於兩個物件。
另外,除了實體可以繼承一個原型之外,原型也可以繼承另外一個原型喔!
那麼在另外一個原型的部分也可以擁有自己的屬性以及方法,那麼這樣的繼承關係可以一段一段的向上繼承,這樣的繼承狀態我們又稱為原型鏈。
再來,這個實體我們要取用其中的屬性的時候,會使用點運算子的方法進行取用,例如 obj.Prop1
、obj.Prop2
而當我們要取用的屬性或是方法沒有在這個實體上的時候,就會透過原型鏈向上查找,直到找到這個原型鏈的頂端為止。
除此之外呢,原型還有另一個特色,就是
如果用同一個原型新增了兩個實體,這兩個實體就會共用同一個原型繼承,共用相同的屬性名稱以及相同的方法。
接下來總結一下觀念:
那麼我們再來就透過程式碼的運作來進行剛剛上面的觀念驗證吧!
var a = [1, 2, 3];
console.log(a);
執行之後我們把這個陣列打開來看
可以看到裡面有對應 0 1 2的屬性,因為我們知道陣列的本質其實也是物件,所以這裡的 0 1 2其實也是這個陣列的物件屬性。當然 length 也是。
所以物件的屬性的話我們有兩種取值的方式,一個是 []
一個是 .
那麼這個陣列現在是屬於一個實體,我們可以透過 proto 的屬性 來看看它陣列的原型
可以看到陣列的原型中有許多的方法,那麼我們剛剛也提到可以透過點運算子取用他的方法。
那麼這邊我們選用的是 forEach 的方法,透過這個方法可以將陣列的每個值都遍歷過一遍。
那麼這個 forEach 就不是屬於 a 這個實體的方法,而是屬於陣列原型的屬性方法。
還有我們剛剛也有講到原型是共用的,所以我們再來新增 b 這個新的陣列 為 [4, 5 , 6]
我們剛剛也有提到 proto 的屬性是指向陣列的原型,所以我們照理說也可以用 proto 的屬性新增方法到陣列的原型上,讓 b 陣列也可以取用到相同的方法。
PS: proto 的屬性雖然可以達成一樣的效果,但一般我們還是不建議這樣使用,後面的章節會教大家使用 prototype 的屬性將要新增的 function 掛載到原型上,讓其他實體也可以取用到。
那麼這邊我們把陣列最後一個值取出來的方法透過 proto 的屬性掛載到陣列的原型上。
另外我們就真的實用這個 getLast 的方法看是否有成功
很明顯就成功的印出我們想要的內容。
我們剛剛說到,打開a 或 b 的 proto 的屬性能夠找到陣列的原型。
那麼繼續往下找又可以找到另一個 proto 的屬性,再把它打開以後會看到的是物件的原型。
因此這個陣列的原型其實是繼承了物件的原型。
而繼續往下看就可以看到物件的一些方法,但到最底就沒有 proto 的屬性,代表說這個物件的原型就是原型鏈的最頂層了!
好,我們再來看一些code。
我們新增了一個新的物件,並且對該物件的物件原型上掛載 getName 的方法,而之後打開 a 跟 b 的原型鏈最頂端的物件原型,也可以找到 getName 的方法的方法喔!
那麼接下來我們就試著在陣列中使用這個 getName 的方法。
首先我們先對 b 這個陣列加入了 name 的屬性,之後我們預期用 getName 應該可以抓到我們剛剛設定的 name 的內容
果不其然成功了,但陣列上並沒有 getName 的方法,所以透過向上查找的方式,在物件的原型上找到了 getName 的方法並且使用它。
那麼這篇文章就介紹了原型以及原型鏈的概念以及實作。
如果沒有問題的話就繼續往下巴~!汪汪